from django.db import models
from inventory.rendering import RenderedComponent
from search.core import SearchManager, porter_stemmer

class Material(models.Model):
  name = models.CharField(max_length=200)

  notes = models.TextField(max_length=204)

  # True if this an accord (vs a primary material -- accords are composed
  # of primary materials and other accords).
  is_accord = models.BooleanField(default=False, editable=False)

  bottle_num = models.IntegerField(blank=True, null=True)

  ml_on_hand = models.IntegerField(blank=True, null=True)

  # Price per ml
  price_ml = models.FloatField(blank=True, null=True)

  # substantivity, in hours
  substantivity = models.IntegerField(blank=True, null=True)

  # TODO: Optimize the index. See
  # http://www.allbuttonspressed.com/projects/nonrel-search
  search_index = SearchManager(('name', 'notes'), indexer=porter_stemmer)

  def GetTotalAmount(self):
    total = 0.0
    if not self.is_accord:
      print 'ERROR: GetTotalAmount of non-accord'
    for comp in self.component_set.all():
      total += comp.amount
    return total

  def GetRenderedComponents(self, scale, ids_seen):
    """Recursive helper for rendering accords."""
    if not self.is_accord:
      return None
    # map of id to RenderedComponent
    component_map = {}
    components = self.component_set.all()
    for comp in components:
      subaccord = comp.component
      if subaccord.is_accord:
        if subaccord.id not in ids_seen:
          # Recurse over sub-accords
          sub_amount = subaccord.GetTotalAmount()
          if sub_amount == 0:
            print 'ERROR: total amount is zero: %s' % subaccord
          new_scale = scale * comp.amount * comp.dilution / sub_amount
          subcomponent_map = subaccord.GetRenderedComponents(
              new_scale, ids_seen)
          self.MergeComponents(subcomponent_map, component_map)
          ids_seen.add(subaccord.id)
        else:
          # TODO logging
          print 'Loop detected: ', comp
      else:
        # Primary material.
        rendered_comp = RenderedComponent(subaccord)
        rendered_comp.rendered_amount = scale * comp.amount * comp.dilution
        component_map[subaccord.id] = rendered_comp
    return component_map

  def MergeComponents(self, m1, m2):
     """Merge contents of m1 into m2."""
     for id, comp in m1.items():
       if id in m2:
         m2[id].rendered_amount += comp.rendered_amount
       else:
         m2[id] = comp

  def __unicode__(self):
    return self.name

  class Admin:
    pass


class Component(models.Model):
  # Accord that this component is a part of.
  accord = models.ForeignKey(Material,
                             related_name='component_set',
                             editable=False)

  # The Material in question.
  component = models.ForeignKey(Material,
                                related_name='material_set')

  # Amount of this component in the accord, in arbitrary scale-free units.
  amount = models.FloatField(default=0)

  # Dilution, as a fraction of 1.0
  dilution = models.FloatField(default=1.0)

  def __unicode__(self):
    dil = ''
    if self.dilution < 1:
      dil = ' @%d%%' % (self.dilution * 100)
    return '%s (%.2f%s)' % (self.component.__unicode__(), self.amount, dil)

  class Admin:
    pass


#################

class WeightedVector(dict):
  def add_weight(self, item, weight):
    self[item] = self.get(item, 0) + float(weight)

  def total(self):
    total_weight = 0
    for x in self:
      total_weight = total_weight + self[x]
    return total_weight

  def normalize(self):
    total_weight = self.total()
    if total_weight == 0:
      return
    for x in self:
      self[x] = self[x] / total_weight

  def scale(self, weight):
    for x in self:
      self[x] = self[x] * weight

  def add(self, other):
    for x in other:
      self.add_weight(x, other[x])
    
